//this file is part of notepad++ 
//Copyright (C)2003 Don HO <donho@altern.org> 
// 
//This program is free software; you can redistribute it and/or 
//modify it under the terms of the GNU General Public License 
//as published by the Free Software Foundation; either 
//version 2 of the License, or (at your option) any later version. 
// 
//This program is distributed in the hope that it will be useful, 
//but WITHOUT ANY WARRANTY; without even the implied warranty of 
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
//GNU General Public License for more details. 
// 
//You should have received a copy of the GNU General Public License 
//along with this program; if not, write to the Free Software 
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 

#include "PluginDefinition.h" 
#include "menuCmdID.h" 
#include "Scintilla.h" 
#include <Process.h>
#include <math.h>



// 
// The plugin data that Notepad++ needs 
// 
FuncItem funcItem[nbFunc]; 

// 
// The data of Notepad++ that you can use in your plugin commands 
// 
NppData nppData; 

// LOCALS 
struct ColorRGB
{
	int R;
	int G;
	int B;
};

static const int SCROLLER_WIDTH = 100;

typedef int (* SCINTILLA_FUNC) (void*, int, int, int); 
typedef void * SCINTILLA_PTR; 

SCINTILLA_FUNC	m_pScintillaFunc; 
SCINTILLA_PTR	m_pScintillaPtr; 

WNDCLASS	m_ClsScroller;

volatile	HWND m_hScintilla;
volatile	HWND m_hScroller;

RECT		m_rScrollerRect;

volatile	int m_nScollerPosX;
			int m_nScollerPosY;
			int m_nScrollerWidth;
volatile	int m_nScrollerHeight;


volatile	HBITMAP codeImg1;
volatile	HDC codeImgHDC1;

volatile	HBITMAP codeImg2;
volatile	HDC codeImgHDC2;

volatile	bool m_bAllowImgUpdate;

int numPixelsY;

// 
// Initialize your plugin data here 
// It will be called while plugin loading    
void pluginInit(HANDLE hModule) 
{ 
} 

// 
// Here you can do the clean up, save the parameters (if any) for the next session 
// 
void pluginCleanUp() 
{ 
	
} 

// 
// Initialization of your plugin commands 
// You should fill your plugins commands here 
void commandMenuInit() 
{ 

	//--------------------------------------------// 
	//-- STEP 3. CUSTOMIZE YOUR PLUGIN COMMANDS --// 
	//--------------------------------------------// 
	// with function : 
	// setCommand(int index,                      // zero based number to indicate the order of command 
	//            TCHAR *commandName,             // the command name that you want to see in plugin menu 
	//            PFUNCPLUGINCMD functionPointer, // the symbol of function (function pointer) associated with this command. The body should be defined below. See Step 4. 
	//            ShortcutKey *shortcut,          // optional. Define a shortcut to trigger this command 
	//            bool check0nInit                // optional. Make this menu item be checked visually 
	//            ); 
	setCommand(0, TEXT("Enable"), Enable, NULL, false); 
	setCommand(1, TEXT("Disable"), Disable, NULL, false); 
} 

// 
// Here you can do the clean up (especially for the shortcut) 
// 
void commandMenuCleanUp() 
{ 
	// Don't forget to deallocate your shortcut here 
} 

template <class T>
T Clamp(T val, T min, T max) //C++ function template sample
{
	if ( val < min )
		val = min;
	else if ( val > max )
		val = max;
	return val;
}

// 
// This function help you to initialize your plugin commands 
// 
bool setCommand(size_t index, TCHAR *cmdName, PFUNCPLUGINCMD pFunc, ShortcutKey *sk, bool check0nInit)  
{ 
	if (index >= nbFunc) 
		return false; 

	if (!pFunc) 
		return false; 

	lstrcpy(funcItem[index]._itemName, cmdName); 
	funcItem[index]._pFunc = pFunc; 
	funcItem[index]._init2Check = check0nInit; 
	funcItem[index]._pShKey = sk; 

	return true; 
} 

LRESULT ExecuteScintilla(UINT Msg, WPARAM wParam=0, LPARAM lParam=0)
{ 
	return SendMessage(m_hScintilla, Msg, wParam, lParam);
}; 

int GetMaxLineLength()
{
	int lineCount = 0; 
	int maxwidth = 0;
	lineCount = (int)ExecuteScintilla(SCI_GETLINECOUNT); 
	for (int y = 0; y < lineCount; ++y) 
	{ 
		int charCount = 0; 
		charCount = (int)ExecuteScintilla(SCI_LINELENGTH, y, 0); 
		if (charCount > maxwidth) 
		{ 
			maxwidth = charCount;
		}
	}

	return maxwidth;
}

int GetLineCount()
{
	return (int)ExecuteScintilla(SCI_GETLINECOUNT);
}

int GetLineLength(int line)
{
	return (int)ExecuteScintilla(SCI_LINELENGTH, line, 0);
}

ColorRGB GetColor(DWORD color)
{
	ColorRGB retVal;
	retVal.B = (color & 0x00FF0000) >> 16;
	retVal.G = (color & 0x0000FF00) >> 8;
	retVal.R = (color & 0x000000FF);
	return retVal;
}

bool StillSane()
{
	return (m_hScintilla != NULL && m_hScroller != NULL && codeImgHDC1 != NULL && codeImg1 != NULL && codeImgHDC2 != NULL && codeImg2 != NULL);
}

DWORD WINAPI UpdateScroller(LPVOID)
{
	while (1)
	{
		// make sure everything is ok
		if ( !StillSane() )
			return 0;

		// move window
		PAINTSTRUCT p;
		RECT rc; 
		GetClientRect( m_hScintilla, &rc); 
		m_nScollerPosX = rc.right - m_nScrollerWidth; 
		m_nScrollerHeight = rc.bottom; 
		MoveWindow( m_hScroller, m_nScollerPosX, m_nScollerPosY, m_nScrollerWidth, m_nScrollerHeight, false );
		SelectObject( codeImgHDC2, codeImg2 );
		BitBlt( GetDC(m_hScroller), 0, 0, m_nScrollerWidth, m_nScrollerHeight, codeImgHDC2, 0, 0, SRCCOPY );
		Sleep(30); // avg around 60 fps
	}

	return 0;
}

DWORD WINAPI UpdateCodeImg(LPVOID)
{
	while (1)
	{
		// make sure everything is ok
		if ( !StillSane() )
			return 0;

		// this is the scroller rect
		RECT scinRC; 
		GetClientRect( m_hScintilla, &scinRC); 

		RECT rc;
		rc.top = 0;
		rc.left = 0;
		rc.right = m_nScrollerWidth;
		rc.bottom = scinRC.bottom;

		// create bitmap to draw to
		SelectObject( codeImgHDC1, codeImg1 );

		// fill in white background
		FillRect( codeImgHDC1, &rc, (HBRUSH)WHITE_BRUSH );

		numPixelsY = 0;
		const int lineCount = ExecuteScintilla(SCI_GETLINECOUNT, 0, 0);
		const int maxLineWidth = GetMaxLineLength();

		float xratio = 1.0f;
		if (maxLineWidth > m_nScrollerWidth)
		{
			xratio = 1.0f / ((float)maxLineWidth / (float)m_nScrollerWidth);
		}
		float yratio = 1.0f;
		if ( lineCount > m_nScrollerHeight )
		{
			yratio = 1.0f / ((float)lineCount / (float)m_nScrollerHeight);
		}

		// draw to bitmap
		for (int line = 0; line < lineCount; ++line) 
		{ 
			const int lineLength = ExecuteScintilla(SCI_LINELENGTH, line, 0);
			for ( int chr = 0; chr < lineLength; ++chr )
			{
				const int linepos = ExecuteScintilla(SCI_POSITIONFROMLINE, line, 0);
				const unsigned int currChar = ExecuteScintilla(SCI_GETCHARAT, linepos + chr, 0);
				if ( currChar > 32 && currChar != 127 )
				{
					const int idStyle = ExecuteScintilla(SCI_GETSTYLEAT, linepos + chr, 0);
					const DWORD fgColor = ExecuteScintilla(SCI_STYLEGETFORE, idStyle, 0);

					const int newx = (float)chr * xratio;
					const int newy = (float)line * yratio;

					SetPixel( codeImgHDC1, newx, newy, fgColor );
				}
			}
		} 

		BitBlt( codeImgHDC2, 0, 0, m_nScrollerWidth, m_nScrollerHeight, codeImgHDC1, 0, 0, SRCCOPY );
		Sleep(250);
	}

	return 0;
}

LRESULT CALLBACK ScrollerProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{ 
	switch(msg) 
	{ 
		case WM_LBUTTONUP: 
		{ 
			int lineCount = 0; 
			lineCount = (int)ExecuteScintilla(SCI_GETLINECOUNT); 
			POINT p; 
			GetCursorPos( &p ); 
			ScreenToClient( hwnd, &p ); 
			float percLine = (float)p.y / (float)numPixelsY;
			if ( percLine > 1.0f )
				percLine = 1.0f;
			int gotoline = (percLine * lineCount); 
			if (gotoline > lineCount)
				gotoline = lineCount;
			SendMessage( m_hScintilla, SCI_GOTOLINE, gotoline, 0 ); 
			break; 
		} 

		case WM_ERASEBKGND:
		case WM_PAINT:
		{
			return 1;
		}

		default: 
			return DefWindowProc(hwnd, msg, wParam, lParam); 
	} 

	return 0; 
} 

bool SetupScintilla()
{
	bool bResult = false;

	// get the current scintilla  handle
	int which = -1; 
	SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&which); 
	
	// make sure we have a valid index
	if ( which != -1 )
	{
		// store handle
		m_hScintilla = (which == 0) ? nppData._scintillaMainHandle : nppData._scintillaSecondHandle; 

		// make sure everything is ok
		if ( m_hScintilla != NULL )
		{
			bResult = true;
		}
	}

	return bResult;
}

bool CreateScrollWindow()
{
	bool bResult = false;

	// set margin
	SendMessage( m_hScintilla, SCI_SETMARGINRIGHT, 0, SCROLLER_WIDTH ); 

	// get scintilla rect
	RECT ScintillaRect; 
	GetClientRect( m_hScintilla, &ScintillaRect); 

	// calculate scroller dimension
	m_nScrollerWidth = SCROLLER_WIDTH;
	m_nScrollerHeight = ScintillaRect.bottom;

	// calculate scroller position
	m_nScollerPosX = ScintillaRect.right - m_nScrollerWidth; 
	m_nScollerPosY = 0; 

	// setup window class
	m_ClsScroller.cbClsExtra =  0; 
	m_ClsScroller.cbWndExtra = 0; 
	m_ClsScroller.lpszClassName = TEXT("NOTESCROLLPP"); 
	m_ClsScroller.style = CS_OWNDC; 
	m_ClsScroller.lpfnWndProc = ScrollerProc; 
	m_ClsScroller.hCursor = LoadCursor(NULL, IDC_ARROW);
	m_ClsScroller.hIcon = NULL;
	m_ClsScroller.lpszMenuName = NULL; 
	m_ClsScroller.hbrBackground = (HBRUSH)BLACK_BRUSH; 
	m_ClsScroller.hInstance = (HINSTANCE)GetWindowLong(m_hScintilla, GWL_HINSTANCE); 

	// create window
	if ( !FAILED(RegisterClass(&m_ClsScroller)) ) 
	{ 
		m_hScroller = CreateWindowEx( NULL, m_ClsScroller.lpszClassName, TEXT("TEST"), WS_CHILDWINDOW|WS_VISIBLE, m_nScollerPosX, m_nScollerPosY, m_nScrollerWidth, m_nScrollerHeight, m_hScintilla, 0, 0, 0 ); 
		if ( m_hScroller != NULL ) 
		{ 
			bResult = true;
		}
	} 

	return bResult;
}

bool CreateCodeImgBitmap()
{
	BITMAPINFO bi;
	memset(&bi, 0, sizeof(bi));
	bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
	bi.bmiHeader.biWidth = m_nScrollerWidth;
	bi.bmiHeader.biHeight = m_nScrollerHeight;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;
	unsigned int* backBufferBits = 0;

	// back buffer
	codeImgHDC1 = CreateCompatibleDC( GetDC(m_hScroller) );
	codeImg1 = CreateDIBSection(0, &bi, DIB_RGB_COLORS, (void**)&backBufferBits, 0, 0);

	// front buffer
	BITMAPINFO bi2;
	memset(&bi2, 0, sizeof(bi2));
	bi2.bmiHeader.biSize = sizeof(bi2.bmiHeader);
	bi2.bmiHeader.biWidth = m_nScrollerWidth;
	bi2.bmiHeader.biHeight = m_nScrollerHeight;
	bi2.bmiHeader.biPlanes = 1;
	bi2.bmiHeader.biBitCount = 32;
	bi2.bmiHeader.biCompression = BI_RGB;
	unsigned int* backBufferBits2 = 0;

	codeImgHDC2 = CreateCompatibleDC( GetDC(m_hScroller) );
	codeImg2 = CreateDIBSection(0, &bi2, DIB_RGB_COLORS, (void**)&backBufferBits2, 0, 0);

	return ( codeImgHDC1 != NULL && codeImg1 != NULL && codeImgHDC2 != NULL && codeImg2 != NULL );
}

//----------------------------------------------// 
//-- STEP 4. DEFINE YOUR ASSOCIATED FUNCTIONS --// 
//----------------------------------------------// 
void Enable() 
{ 
	// setup scintilla
	if ( SetupScintilla() )
	{
		// create window
		CreateScrollWindow();

		// create bitmap
		CreateCodeImgBitmap();

		// this thread updates the scroller position and blits the latest code img to the control
		CreateThread( NULL, 0, UpdateScroller, NULL, 0, NULL);

		// this htread updates the actual code image
		CreateThread( NULL, 0, UpdateCodeImg, NULL, 0, NULL);
	}
} 

void Disable() 
{ 
	if ( m_hScintilla != NULL )
	{
		// reset scintilla
		SendMessage( m_hScintilla, SCI_SETMARGINRIGHT, 0, 0 ); 
	}

	if ( m_hScroller != NULL )
	{
		// destroy scroller
		DestroyWindow( m_hScroller ) ;
	}

	// unregister our class
	UnregisterClass(m_ClsScroller.lpszClassName, m_ClsScroller.hInstance);
} 
